// Copyright 2012 dmitry.a.novikov@gmail.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// CnsZ::ValueString declaration


#ifndef _INCLUDED_CNZ_VALUEARRAY_H_
#define _INCLUDED_CNZ_VALUEARRAY_H_

#include "object.h"
#include "filer.h"
#include  <algorithm>

namespace CnsZ {

  class String;

#define DECLARE_CNSZ_ARRAY_CLASS_ALLOC( ClassName, TElem, TAloc ) \
  class ClassName : public Array<TElem,TAloc> {\
  public:\
    const Class* isA() const { return classInfo(); } \
    static const Class* classInfo() {\
      static class ClassImpl : public ImplNoRefCount<Class> {\
      public:\
        const AString& name() const {\
          CNSZ_DEFINE_STATIC_ASTRING(className, #ClassName);\
          return CNSZ_GET_STATIC_STRING(className);\
        }\
        P<Object> createInstance() const { return ClassName::Empty(); }\
        const Class* parentClass() const { return Object::classInfo(); }\
      } classImpl;\
      return &classImpl;\
    }\
  }

  template <class Elem, class Traits>
  class Array;

  template <class Elem>
  class ArrayClassName {
  public:
    static const AString& get() {
      static AString s_empty;
      return s_empty;
    }
  };

  template <class Elem, class Traits>
  class BaseArrayTraits {
  public:
    static P<Array<Elem, Traits> > NewArray(size_t addmem) {
      return ImplRefCount<Array<Elem, Traits> >::New(addmem);
    }

    typedef Elem* iterator;
    typedef const Elem* const_iterator;
    typedef Elem& reference;
    typedef const Elem& const_reference;

    static void construct(iterator elem, const_reference ini) {
      new (elem) Elem(ini);
    }
    static void construct(iterator beg, iterator end, const_reference ini) {
      while(beg<end) { construct(beg++, ini); }
    }
    static void construct(iterator beg, iterator end, const_iterator iniiter) {
      while(beg<end) { construct(beg++, *iniiter++); }
    }
    static void move_up(iterator srcbeg, iterator srcend, iterator dstend) {
      while(srcend-->srcbeg) {
        construct(--dstend, *srcend);
        destruct(srcend);
      }
    }
    static void move_down(iterator srcbeg, iterator srcend, iterator dstbeg) {
      while(srcbeg<srcend) {
        construct(dstbeg++, *srcbeg);
        destruct(srcbeg++);
      }
    }
    static void destruct(iterator elem) {
      elem->~Elem();
    }
    static void destruct(iterator beg, iterator end) {
      while(beg<end) { destruct(beg++); }
    }
  };

  template <class Elem>
  class DefaultTraits : public BaseArrayTraits<Elem, DefaultTraits<Elem> > {
  public:
  };

  template <> class ArrayClassName<double> {
  public:
    static const AString& get() {
      CNSZ_DEFINE_STATIC_ASTRING(g_name, "DoubleArray");
      return CNSZ_GET_STATIC_ASTRING(g_name);
    }
  };

  template <>
  class DefaultTraits<double> : public BaseArrayTraits<double, DefaultTraits<double> > {
  public:
    static void move_up(iterator srcbeg, iterator srcend, iterator dstend) {
      while(srcend-->srcbeg) {
        construct(--dstend, *srcend);
      }
    }
    static void move_down(iterator srcbeg, iterator srcend, iterator dstbeg) {
      while(srcbeg<srcend) {
        construct(dstbeg++, *srcbeg++);
      }
    }
    static void destruct(iterator , iterator ) {
    }
  };

  template <> class ArrayClassName<UInt8> { public:
    static const AString& get() {
      CNSZ_DEFINE_STATIC_ASTRING(g_name, "UInt8Array");
      return CNSZ_GET_STATIC_ASTRING(g_name);
    }
  };

  template <>
  class DefaultTraits<UInt8> : public BaseArrayTraits<UInt8, DefaultTraits<UInt8> > {
  public:
    static void move_up(iterator srcbeg, iterator srcend, iterator dstend) {
      while(srcend-->srcbeg) {
        construct(--dstend, *srcend);
      }
    }
    static void move_down(iterator srcbeg, iterator srcend, iterator dstbeg) {
      while(srcbeg<srcend) {
        construct(dstbeg++, *srcbeg++);
      }
    }
    static void destruct(iterator , iterator ) {
    }
  };

  template <class Elem, class Traits = DefaultTraits<Elem> >
  class Array : public Object {
    size_t sz;
    size_t allocated;
  public:
    typedef Elem* iterator;
    typedef const Elem* const_iterator;
    typedef Elem& reference;
    typedef const Elem& const_reference;

    const Class* isA() const { return classInfo(); }

    static const Class* classInfo() {
      static class ClassImpl : public ImplNoRefCount<Class> {
      public:
        const AString& name() const { return ArrayClassName<Elem>::get(); }
        const Class* parentClass() const { return Object::classInfo(); }
        P<Object> createInstance() const { NULL_FACTORY(Array); }
      } s_info;
      return &s_info;
    }

  protected:
    struct Writer {
      Filer* f;
      Writer(Filer& filer) : f(&filer) {}
      void operator()(const_reference elem) {
        (*f) << elem;
      }
    };
    struct Reader {
      Filer* f;
      Reader(Filer& filer) : f(&filer) {}
      void operator()(Elem& elem) {
        (*f) >> (Elem&)elem;
      }
    };
    void checkIndex(size_t i) { if(i>=size()) throw IndexOutOfBounds(); }
    void checkIndex(const_iterator iter) { if(iter<begin() || iter>=end()) throw IndexOutOfBounds(); }
    void checkInsertPos(const_iterator iter) { if(iter<begin() || iter>end()) throw IndexOutOfBounds(); }
    Array* setcapacity(size_t msz) { allocated = msz; return this; }
    Array* init(size_t uptosize, const_reference ini = Elem());
    Array* init(const_iterator inibeg, const_iterator iniend);
    void uninit(size_t downtosize);

    Array() : sz(0) {}
    ~Array() { Traits::destruct(begin(),end()); }
  public:
    size_t size() const { return sz; }
    size_t capacity() const { return allocated; }

    const_iterator begin() const { return reinterpret_cast<const_iterator>(this+1); }
    iterator begin() { return reinterpret_cast<iterator>(this+1); }

    const_iterator end() const { return begin() + sz; }
    iterator end() { return begin() + sz; }

    const_reference at(size_t i) const { checkIndex(i); return (*(begin() + i)); }
    reference at(size_t i) { checkIndex(i); return (*(begin() + i)); }

    static void resize(P<Array<Elem, Traits> >& _this, size_t newsize, const_reference ini=Elem()) {
      if(newsize<=_this->capacity() && _this->numRefs()==1) {
        if(newsize < _this->size()) {
          Traits::destruct(_this->begin()+newsize, _this->end());
        } else {
          Traits::construct(_this->end(), _this->begin()+newsize, ini);
        }
        _this->sz = newsize;
      } else {
        P<Array<Elem, Traits> > _new = New(newsize);
        _new->sz = _this->sz;
        Traits::construct(_new->begin(), _new->end(), _this->begin());
        Traits::construct(_new->end(), _new->begin()+newsize, ini);
        _new->sz = newsize;
        _this = _new;
      }
    }

    static void reserve(P<Array<Elem, Traits> >& _this, size_t minCapacity) {
      if(minCapacity > _this->capacity()) {
        P<Array<Elem, Traits> > _new = New(minCapacity);
        _new->sz = _this->sz;
        Traits::construct(_new->begin(), _new->end(), _this->begin());
        _this = _new;
      }
    }

    static void insert(P<Array<Elem, Traits> >& _this, iterator _where, const_iterator inibeg, const_iterator iniend) {
      _this->checkInsertPos(_where);
      size_t num = iniend-inibeg;
      size_t newsize = _this->size()+num;
      if(newsize <= _this->capacity() && _this->numRefs()==1) {
        Traits::move_up(_where, _this->end(), _this->end()+num);
        Traits::construct(_where, _where+num, inibeg);
        _this->sz = newsize;
      } else {
        P<Array<Elem, Traits> > _new = New(newsize);
        _new->sz = _where-_this->begin();
        Traits::construct(_new->begin(), _new->end(), _this->begin());
        Traits::construct(_new->end(), _new->end()+num, inibeg);
        _new->sz += num;
        Traits::construct(_new->end(), _new->begin()+newsize, _where);
        _new->sz = newsize;
        _this = _new;
      }
    }

    static iterator erase(P<Array<Elem, Traits> >& _this, iterator beg, iterator end) {
      _this->checkInsertPos(beg);
      _this->checkInsertPos(end);
      size_t num = end-beg;
      if(_this->numRefs()==1) {
        Traits::move_down(end, _this->end(), beg);
        _this->sz -= num;
        Traits::destruct(_this->end(), _this->end()+num);
      } else {
        P<Array<Elem, Traits> > _new = New(_this->size()+num);
        _new->sz = beg - _this->begin();
        Traits::construct(_new->begin(), _new->end(), end);
        Traits::construct(_new->end(), _new->begin()+_this->size()-_new->size()-num, end);
        _new->sz = _new->allocated;
        _this = _new;
      }
      return _this->begin();
    }

    int compare(const Array* other) const;
    /*
    static P<Array> read(Filer& filer) {
      Reader reader(filer);
      size_t sz;
      filer >> sz;
      P<Array> res = New(sz);
      std::for_each(res->begin(), res->end(), reader);
      return res;
    }

    void write(Filer& filer) const {
      Writer writer(filer);
      filer << UInt64(size());
      std::for_each(begin(), end(), writer);
    }
    */
    static P<Array> New(size_t allocated);

	static const Array* Empty() {
		static ImplNoRefCount<Array> emp;
		return &emp;
	}

    String toString() const;
  };

  template <class Elem, class Traits=DefaultTraits<Elem> >
  class A {
  protected:
    P<Array<Elem, Traits> > arr;
  public:
    typedef Elem* iterator;
    typedef const Elem* const_iterator;
    typedef Elem& reference;
    typedef const Elem& const_reference;

    A();
    A(const Array<Elem, Traits>* d) : arr(d) {}
    explicit A(size_t count);
    A(size_t count, const_reference val);
    A(const_iterator first, const_iterator last);

    const_iterator begin() const { return arr->begin(); }
    iterator begin() { return arr->begin(); }

    const_iterator end() const { return arr->end(); }
    iterator end() { return arr->end(); }

    size_t size() const { return arr->size(); }
    size_t capacity() const { return arr->capacity(); }

    void resize(size_t newsize, const_reference ini=Elem()) {
      Array<Elem, Traits>::resize(arr, newsize, ini);
    }

    void reserve(size_t minCapacity) {
      Array<Elem, Traits>::reserve(arr, minCapacity);
    }

    bool empty() const { return (size() == 0); }

    const_reference at(size_t i) const { return arr->at(i); }
    reference at(size_t i) { return arr->at(i); }

    const_reference operator[](size_t i) const { return arr->at(i); }
    reference operator[](size_t i) { return arr->at(i); }

    const_reference front() const { return *begin(); }
    reference front() { return *begin(); }

    const_reference back() const { return *(end()-1); }
    reference back() { return *(end()-1); }

    void push_back(const_reference e) { resize(size()+1, e); }
    void pop_back() { if (!empty()) erase(end() - 1); }

    iterator erase(iterator _where);
    iterator erase(iterator beg, iterator end) {
      return Array<Elem, Traits>::erase(arr, beg, end);
    }

    void insert(iterator before, const_iterator beg, const_iterator end) {
      Array<Elem, Traits>::insert(arr, before, beg, end);
    }
    iterator insert(iterator before, const_reference val) {
      size_t pos = before - arr->begin();
      Array<Elem, Traits>::insert(arr, before, &val, (&val)+1);
      return arr->begin()+pos;
    }
    void insert(iterator before, size_t count, const_reference val);

    A& operator=(const Array<Elem, Traits>* arr);
    bool operator==(const Array<Elem, Traits>* arr) const;

    const P<Array<Elem, Traits> >& valueArray() const { return arr; }
    P<Array<Elem, Traits> >& valueArray() { return arr; }
  };

  template <class Elem, class Traits>
  inline A<Elem, Traits>::A()
    : arr(Array<Elem, Traits>::Empty()) {
  }
 
  template <class Elem, class Traits>
  inline Array<Elem, Traits>* Array<Elem, Traits>::init(size_t uptosize, const_reference ini) {
    ASSERT(uptosize<=allocated, "Array::init()");
    Traits::construct(end(), begin()+uptosize, ini);
    allocated = sz = uptosize;
    return this;
  }
 
  template <class Elem, class Traits>
  inline Array<Elem, Traits>* Array<Elem, Traits>::init(const_iterator inibeg, const_iterator iniend) {
    size_t n = iniend-inibeg;
    iterator beg = begin();
    Traits::construct(beg, beg+n, inibeg);
    ASSERT(uptosize<=allocated, "Array::init()");
    allocated = sz = n;
    return this;
  }
 
  template <class Elem, class Traits>
  inline void Array<Elem, Traits>::uninit(size_t downtosize) {
    Traits::destruct(begin()+downtosize, end());
    ASSERT(downtosize<=allocated, "Array::uninit()");
    sz = downtosize;
    return this;
  }

  template <class Elem, class Traits>
  inline P<Array<Elem, Traits> > Array<Elem, Traits>::New(size_t allocated) {
    return Traits::NewArray(sizeof(Elem)*allocated)->setcapacity(allocated);
  }
  
  template <class Elem, class Traits>
  inline A<Elem, Traits>::A(size_t count)
    : arr(Array<Elem, Traits>::New(count))
  {
    resize(count);
  }
  
  template <class Elem, class Traits>
  inline A<Elem, Traits>::A(size_t count, const_reference val)
    : arr(Array<Elem, Traits>::New(count, val)) {
  }

  template <class Elem, class Traits>
  inline A<Elem, Traits>& A<Elem, Traits>::operator = (const Array<Elem, Traits>* arr) {
    assign(arr);
    return *arr;
  }

  template <class Elem, class Traits>
  inline String Array<Elem, Traits>::toString() const {
    std::wostringstream s;
    s << "[(" << size() << ',' << capacity() << "):";
    const_iterator c = begin(), e = end();
    while(c<e) {
      s << *c;
      ++c;
      if(c<e) {
        s << ',';
      }
    }
    s << ']';
    return String(s.str().data());
  }

} //namespace CnsZ

template <class Elem, class Traits>
std::wostream& operator<<(std::wostream& ostr, const CnsZ::A<Elem, Traits>& a) {
  return ostr << a.valueArray();
}

template <class Elem, class Traits>
CnsZ::Filer& operator<<(CnsZ::Filer& f, const CnsZ::A<Elem, Traits>& a) {
  a.valueArray()->write(f);
  return f;
}

template <class Elem, class Traits>
CnsZ::Filer& operator>>(CnsZ::Filer& f, CnsZ::A<Elem, Traits>& a) {
  a.valueArray()->read(f);
  return f;
}

#endif //#ifndef _INCLUDED_CNZ_VALUEARRAY_H_